package io.gitee.baicaixiaozhan.httpmessageconverter.gson;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import io.gitee.baicaixiaozhan.httpmessageconverter.annotation.Desensitize;
import io.gitee.baicaixiaozhan.httpmessageconverter.kit.ReplaceKit;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

/**
 * DESC: 数据脱敏 Gson 实现
 *
 * @author baicaixiaozhan
 * @since v1.0.0
 */
public class DesensitizeTypeAdapterFactory implements TypeAdapterFactory {

    private static final String VARIABLE = "str";

    private SpelParserConfiguration config;
    private ExpressionParser parser;

    public DesensitizeTypeAdapterFactory() {
        this.config = new SpelParserConfiguration(true, true);
        this.parser = new SpelExpressionParser(this.config);
    }

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        Class<? super T> targetType = type.getRawType();

        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                Arrays.stream(targetType.getDeclaredFields())
                        .filter(field -> field.isAnnotationPresent(Desensitize.class) &&
                                field.getType().isAssignableFrom(String.class))
                        .forEach(field -> {
                            Desensitize desensitize = field.getAnnotation(Desensitize.class);
                            char delimiter = desensitize.delimiter();

                            field.setAccessible(true);
                            String targetValue = (String) ReflectionUtils.getField(field, value);
                            field.setAccessible(false);
                            if (Objects.isNull(targetValue)) {
                                return;
                            }
                            String result;
                            switch (desensitize.pattern()) {
                                case FULL_NAME:
                                    result = ReplaceKit.replace(targetValue, 1, targetValue.length(), delimiter);
                                    break;
                                case ID_CARD:
                                    result = ReplaceKit.replace(targetValue, 1, targetValue.length() - 1, delimiter);
                                    break;
                                case MOBILE_PHONE:
                                    result = ReplaceKit.replace(targetValue, 3, targetValue.length() - 4, delimiter);
                                    break;
                                case FIXED_TELEPHONE:
                                    result = ReplaceKit.replace(targetValue, 4, targetValue.length() - 2, delimiter);
                                    break;
                                case EMAIL:
                                    result = ReplaceKit.replace(targetValue, 1, targetValue.indexOf("@"), delimiter);
                                    break;
                                case RANGE:
                                    result = ReplaceKit.replace(targetValue,
                                            expressionIndex(targetValue, desensitize.start()),
                                            expressionIndex(targetValue, desensitize.end()), delimiter);
                                    break;
                                default:
                                    result = ReplaceKit.replace(targetValue, 0, targetValue.length(), delimiter);
                            }
                            field.setAccessible(true);
                            ReflectionUtils.setField(field, value, result);
                            field.setAccessible(false);
                        });

                delegate.write(out, value);
            }

            @Override
            public T read(JsonReader in) throws IOException {
                return delegate.read(in);
            }
        };
    }

    private Integer expressionIndex(CharSequence source, String expressionString) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable(VARIABLE, source);
        Expression exp = parser.parseExpression(expressionString);
        return exp.getValue(context, Integer.class);
    }

}
